package com.gitee.wsl.ext.json

import com.gitee.wsl.serialization.json.context.JsonContext.Default.decode
import com.gitee.wsl.serialization.json.context.JsonContext.Default.decodeValues
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject


inline fun <reified T> JsonObject.getEnum(key: String): T = getContent(key).decode()
inline fun <reified T> JsonObject.getEnumOrNull(key: String): T? = getContentOrNull(key)?.decode<T>()
fun JsonObject.getContentOrNull(key: String): String? = get(key)?.toContentOrNull()
fun JsonObject.getIntOrNull(key: String): Int? = get(key)?.toIntOrNull()
fun JsonObject.getLongOrNull(key: String): Long? = get(key)?.toLongOrNull()
fun JsonObject.getFloatOrNull(key: String): Float? = get(key)?.toFloatOrNull()
fun JsonObject.getDoubleOrNull(key: String): Double? = get(key)?.toDoubleOrNull()
fun JsonObject.getBooleanOrNull(key: String): Boolean? = get(key)?.toBooleanOrNull()
fun JsonObject.getBooleanOrFalse(key: String): Boolean = getBooleanOrNull(key) ?: false
fun JsonObject.getNumberOrNull(key: String): Number? = get(key)?.toNumberOrNull()

fun JsonObject.getContent(key: String): String = getValue(key).toContent()
fun JsonObject.getInt(key: String): Int = getValue(key).toInt()
fun JsonObject.getLong(key: String): Long = getValue(key).toLong()
fun JsonObject.getFloat(key: String): Float = getValue(key).toFloat()
fun JsonObject.getDouble(key: String): Double = getValue(key).toDouble()
fun JsonObject.getBoolean(key: String): Boolean = getValue(key).toBoolean()


fun <Value> JsonObject.getListOrEmpty(key: String, getValue: (JsonElement) -> Value?): List<Value> {
    val array = get(key)?.toArrayOrNull() ?: return emptyList()
    return array.mapNotNull(getValue)
}

inline fun <reified Value> JsonObject.getEnumListOrEmpty(key: String): List<Value> = getContentListOrEmpty(key).decode()
fun JsonObject.getContentListOrEmpty(key: String): List<String> = getListOrEmpty(key, JsonElement::toContentOrNull)
fun JsonObject.getIntListOrEmpty(key: String): List<Int> = getListOrEmpty(key, JsonElement::toIntOrNull)
fun JsonObject.getLongListOrEmpty(key: String): List<Long> = getListOrEmpty(key, JsonElement::toLongOrNull)
fun JsonObject.getFloatListOrEmpty(key: String): List<Float> = getListOrEmpty(key, JsonElement::toFloatOrNull)
fun JsonObject.getDoubleListOrEmpty(key: String): List<Double> = getListOrEmpty(key, JsonElement::toDoubleOrNull)
fun JsonObject.getBooleanListOrEmpty(key: String): List<Boolean> = getListOrEmpty(key, JsonElement::toBooleanOrNull)

fun <Value> JsonObject.toMap(getValue: (JsonElement) -> Value): Map<String, Value> = mapValues { entry -> getValue(entry.value) }
inline fun <reified Value> JsonObject.toEnumMap(): Map<String, Value> = toContentMap().decodeValues()
fun JsonObject.toContentMap(): Map<String, String> = toMap(JsonElement::toContent)
fun JsonObject.toIntMap(): Map<String, Int> = toMap(JsonElement::toInt)
fun JsonObject.toLongMap(): Map<String, Long> = toMap(JsonElement::toLong)
fun JsonObject.toFloatMap(): Map<String, Float> = toMap(JsonElement::toFloat)
fun JsonObject.toDoubleMap(): Map<String, Double> = toMap(JsonElement::toDouble)
fun JsonObject.toBooleanMap(): Map<String, Boolean> = toMap(JsonElement::toBoolean)


@Suppress("UNCHECKED_CAST")
fun <Value> JsonObject.getMapOrEmpty(key: String, getValue: (JsonElement) -> Value?): Map<String, Value> {
    val jsonObject = get(key)?.jsonObject ?: return emptyMap()
    return jsonObject.mapValues { entry -> getValue(entry.value) }.filterValues { value -> value != null } as Map<String, Value>
}

inline fun <reified Value> JsonObject.getEnumMapOrEmpty(key: String): Map<String, Value> = getContentMapOrEmpty(key).decodeValues()
fun JsonObject.getContentMapOrEmpty(key: String): Map<String, String> = getMapOrEmpty(key, JsonElement::toContentOrNull)
fun JsonObject.getIntMapOrEmpty(key: String): Map<String, Int> = getMapOrEmpty(key, JsonElement::toIntOrNull)
fun JsonObject.getLongMapOrEmpty(key: String): Map<String, Long> = getMapOrEmpty(key, JsonElement::toLongOrNull)
fun JsonObject.getFloatMapOrEmpty(key: String): Map<String, Float> = getMapOrEmpty(key, JsonElement::toFloatOrNull)
fun JsonObject.getDoubleMapOrEmpty(key: String): Map<String, Double> = getMapOrEmpty(key, JsonElement::toDoubleOrNull)
fun JsonObject.getBooleanMapOrEmpty(key: String): Map<String, Boolean> = getMapOrEmpty(key, JsonElement::toBooleanOrNull)

fun <Value> JsonObject.getObject(key: String, getValue: (JsonObject) -> Value): Value {
    val jsonObject = getValue(key).jsonObject
    return getValue(jsonObject)
}

fun <Value> JsonObject.getObjectOrNull(key: String, getValue: (JsonObject) -> Value?): Value? {
    val jsonObject = get(key)?.toObjectOrNull() ?: return null
    return getValue(jsonObject)
}

fun <Value> JsonObject.getElement(key: String, getValue: (JsonElement) -> Value): Value {
    val jsonElement = getValue(key)
    return getValue(jsonElement)
}

fun <Value> JsonObject.getElementOrNull(key: String, getValue: (JsonElement) -> Value?): Value? {
    val jsonElement = get(key) ?: return null
    return getValue(jsonElement)
}

fun <Value> JsonObject.getObjectMapOrEmpty(
    key: String,
    getValue: (JsonObject) -> Value?,
): Map<String, Value> = getMapOrEmpty(key) { element ->
    element.toObjectOrNull()?.let(getValue)
}

fun <Value> JsonObject.getObjectListOrEmpty(
    key: String,
    getValue: (JsonObject) -> Value?,
) = getListOrEmpty(key) { element ->
    element.toObjectOrNull()?.let(getValue)
}



/**
 * Traverses a JSON object or array using a sequence of keys or array indices from the query list
 * and retrieves the corresponding JSON element. If any key or index is not found or invalid,
 * the function returns `null`.
 *
 * ```kotlin
 * val json = Json.decodeFromString<JsonObject>("""
 * {
 *    "key": "value",
 *    "array": [1, 2, 3]
 * }
 *    """.trimIndent())
 * json.select("key") // Returns a JsonPrimitive("value")
 * json.select("array.$1") // Returns a JsonPrimitive(2)
 * json.select("array.$5") // Returns null
 * ```
 *
 * @param query A dot separated string where each string specifies a key for a JsonObject or
 *              an index (prefixed with '$') for a JsonArray to navigate through the JSON structure.
 * @return The desired JsonElement if found, or null if the key or index does not exist in the JSON structure.
 */
fun JsonObject.select(query: String): JsonElement? = select(query.split("."))

/**
 * Traverses a JSON object or array using a sequence of keys or array indices from the query list
 * and retrieves the corresponding JSON element. If any key or index is not found or invalid,
 * the function returns `null`.
 *
 * Examples:
 *
 * ```kotlin
 * val json = Json.decodeFromString<JsonObject>("""
 * {
 *    "key": "value",
 *    "array": [1, 2, 3]
 * }
 *    """.trimIndent())
 * json.select(listOf("key")) // Returns a JsonPrimitive("value")
 * json.select(listOf("array", "$1")) // Returns a JsonPrimitive(2)
 * json.select(listOf("array", "$5")) // Returns null
 * ```
 *
 * @param query A list of strings where each string specifies a key for a JsonObject or
 *              an index (prefixed with '$') for a JsonArray to navigate through the JSON structure.
 * @return The desired JsonElement if found, or null if the key or index does not exist in the JSON structure.
 */
fun JsonObject.select(query: List<String>): JsonElement? {
    val queue = query.toMutableList()
    var currentElement: JsonElement = this

    while (queue.isNotEmpty()) {
        val currentSegment: String = queue.removeFirst()
        when {
            currentSegment.startsWith("$") && currentElement is JsonArray -> {
                val elementAtIndex = currentSegment.removePrefix("$").toInt()
                currentElement = currentElement.getOrNull(elementAtIndex) ?: return null
            }
            currentElement is JsonObject -> currentElement = currentElement[currentSegment] ?: return null
            else -> return null
        }
    }

    return currentElement
}
